/*******************************************************************************
 * Copyright (c) 2010 European Software Institute - Tecnalia.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Author - Adrian Noguero (adrian.noguero@tecnalia.com)
 *     
 *******************************************************************************/

package es.esi.gemde.modeltransformator.ui.wizards;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Vector;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import es.esi.gemde.modeltransformator.ModelTransformatorPlugin;
import es.esi.gemde.modeltransformator.exceptions.NoSuchEngineException;
import es.esi.gemde.modeltransformator.service.ITransformation;
import es.esi.gemde.modeltransformator.service.TransformationFactory;
import es.esi.gemde.modeltransformator.ui.dialogs.AddTransformationInputDialog;

/**
 * A wizard for creating transformations
 *
 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
 * @version 1.0
 * @since 1.0
 *
 */
public class TransformationCreationWizard extends Wizard {
	
	private boolean canFinish = false;
	private String name;
	private String description;
	private String engine;
	private List<URI> transformationFiles;
//	private boolean checkNeeded = false;
	private boolean transformationFilesCorrect = false;
	private boolean ioParamsCorrect = false;
	private ITransformation createdTransformation;
	private HashMap<String, EClass> inputsMap;
	private HashMap<String, EClass> outputsMap;
	private boolean isEdit = false;
	private HashMap<String, String> options;
	
	/**
	 * Initializes the wizard with default null values. To be used for NEW Transformations.
	 *
	 */
	public TransformationCreationWizard() {
		super();
		setWindowTitle("Create a new Transformation");
		name = null;
		description = null;
		engine = null;
		transformationFiles = new Vector<URI>();
		inputsMap = new LinkedHashMap<String, EClass>();
		outputsMap = new LinkedHashMap<String, EClass>();
		options = new HashMap<String, String>();
		isEdit = false;
	}
	
	/**
	 * Initializes the wizard with a transformation. To be used for EDITING transformations.
	 *
	 *@param transformation the transformation instance that will initialize the wizard
	 */
	public TransformationCreationWizard(ITransformation transformation) {
		super();
		setWindowTitle("Edit a Transformation");
		name = transformation.getName();
		description = transformation.getDescription();
		engine = transformation.getEngine();
		transformationFiles = transformation.getTransformationURIs();
		inputsMap = transformation.getInputs();
		outputsMap = transformation.getOutputs();
		options = transformation.getOptions();
		isEdit = true;
		transformationFilesCorrect = true;
		
	}
	
	/**
	 * Returns the created {@link ITransformation} instance.
	 * 
	 * @return the instance
	 */
	public ITransformation getCreatedTransformation () {
		return createdTransformation;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#performFinish()
	 */
	@Override
	public boolean performFinish() {
		try {
			if (isEdit) {
				// First remove the transformation from the repository to avoid IllegalArgumentException!
				ModelTransformatorPlugin.getService().removeTransformation(name);
			}
			
			createdTransformation = TransformationFactory.createTransformation(name, engine, transformationFiles, description, inputsMap, outputsMap);
			for (Entry<String, String> e : options.entrySet()) {
				createdTransformation.addTransformationOption(e.getKey(), e.getValue());
			}
			
			
		} catch (IllegalArgumentException e) {
			// Should be prevented by the wizard logic!!
			e.printStackTrace();
		} catch (NoSuchEngineException e) {
			// Should be prevented by the wizard logic!!
			e.printStackTrace();
		} catch (IOException e) {
			// Should be prevented by the wizard logic!!
			e.printStackTrace();
		}
		return true;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#addPages()
	 */
	@Override
	public void addPages() {
		addPage(new EditTransformationPage());
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.wizard.Wizard#canFinish()
	 */
	@Override
	public boolean canFinish() {
		return canFinish;
	}
	
	/**
	 * The implementation of the only page of this wizard
	 *
	 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
	 * @version 1.0
	 * @since 1.0
	 *
	 */
	private class EditTransformationPage extends WizardPage {
		
		private static final String NAME = "Edit Validation Page";
		
		private Text nameText;
		//private Text fileText;
		private ListViewer fileList;
		private Button removeFileButton;
		private Text descriptionText;
		private Combo engineCombo;
		private Label helpLabel;
		private ListViewer inputsList;
		private ListViewer outputsList;
		private Button removeInputButton;
		private Button removeOutputButton;
		private Group extraOptionsGroup;
		
		/**
		 * Default Constructor. Calls the constructor of the super-class.
		 *
		 */
		private EditTransformationPage() {
			super(NAME, "Create a Transformation", null);
		}
		
		/* (non-Javadoc)
		 * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
		 */
		@Override
		public void createControl(Composite parent) {
			Composite topPanel = new Composite(parent, SWT.NONE);
			topPanel.setLayout(new GridLayout(6, true));
			
			topPanel.setSize(400, 600);
			
			// Creating controls in order!
			
			Label nameLabel = new Label(topPanel, SWT.NONE);
			GridData nameLabelData = new GridData();
			nameLabelData.horizontalSpan = 1;
			nameLabel.setText("Transformation Name:");
			nameLabel.setLayoutData(nameLabelData);
			
			nameText = new Text(topPanel, SWT.BORDER | SWT.LEFT);
			GridData nameTextData = new GridData(GridData.FILL_HORIZONTAL);
			nameTextData.horizontalSpan = 5;
			nameText.setLayoutData(nameTextData);
			if (name == null) {
				nameText.setText("");
			}
			else {
				nameText.setText(name);
			}
			
			Label fileLabel = new Label(topPanel, SWT.NONE);
			GridData fileLabelData = new GridData();
			fileLabelData.horizontalSpan = 6;
			fileLabel.setText("Transformation Files:");
			fileLabel.setLayoutData(fileLabelData);
			
			fileList = new ListViewer(topPanel, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL| SWT.H_SCROLL);
			GridData fileListData = new GridData(GridData.FILL_BOTH);
			fileListData.horizontalSpan = 6;
			fileList.getList().setLayoutData(fileListData);
			fileList.setLabelProvider(fileLabelProvider);
			if (transformationFiles != null && transformationFiles.size() > 0) {
				for (URI c : transformationFiles) {
					fileList.add(c);
				}
			}
			
			Label emptyLabel2 = new Label(topPanel, SWT.NONE);
			GridData emptyLabelData2 = new GridData();
			emptyLabelData2.horizontalSpan = 4;
			emptyLabel2.setText("");
			emptyLabel2.setLayoutData(emptyLabelData2);
			
			Button addFileButton = new Button(topPanel, SWT.PUSH | SWT.CENTER);
			GridData addFileButData = new GridData(GridData.FILL_HORIZONTAL);
			addFileButData.horizontalSpan = 1;
			addFileButton.setLayoutData(addFileButData);
			addFileButton.setText("Add File");
			
			removeFileButton = new Button(topPanel, SWT.PUSH | SWT.CENTER);
			GridData removeFileButData = new GridData(GridData.FILL_HORIZONTAL);
			removeFileButData.horizontalSpan = 1;
			removeFileButton.setLayoutData(removeFileButData);
			removeFileButton.setText("Remove File");
			removeFileButton.setEnabled(false);
			
			
			Label engineLabel = new Label(topPanel, SWT.NONE);
			GridData engineLabelData = new GridData();
			engineLabelData.horizontalSpan = 1;
			engineLabel.setText("Transformation Engine:");
			engineLabel.setLayoutData(engineLabelData);
			
			engineCombo = new Combo(topPanel, SWT.BORDER | SWT.READ_ONLY);
			GridData engineComboData = new GridData(GridData.FILL_HORIZONTAL);
			engineComboData.horizontalSpan = 2;
			engineCombo.setLayoutData(engineComboData);
			for (String engine : ModelTransformatorPlugin.getService().getAvailableEngines()) {
				if (!engine.equals("Java")) {
					engineCombo.add(engine);
				}
			}
			if (engine != null) {
				engineCombo.select(engineCombo.indexOf(engine));
			}
			else {
				engineCombo.select(0);
			}
			
			Label emptyLabel = new Label(topPanel, SWT.NONE);
			GridData emptyLabelData = new GridData();
			emptyLabelData.horizontalSpan = 3;
			emptyLabel.setText("");
			emptyLabel.setLayoutData(emptyLabelData);
			
			Label descriptionLabel = new Label(topPanel, SWT.NONE);
			GridData descriptionLabelData = new GridData(GridData.FILL_HORIZONTAL);
			descriptionLabelData.horizontalSpan = 6;
			descriptionLabel.setText("Description:");
			descriptionLabel.setLayoutData(descriptionLabelData);
			
			descriptionText = new Text(topPanel, SWT.BORDER | SWT.LEFT);
			GridData descriptionTextData = new GridData(GridData.FILL_BOTH);
			descriptionTextData.horizontalSpan = 6;
			descriptionText.setLayoutData(descriptionTextData);
			if (description == null) {
				descriptionText.setText("");
			}
			else {
				descriptionText.setText(description);
			}
			
			Label inputsLabel = new Label(topPanel, SWT.NONE);
			GridData inputsLabelData = new GridData(GridData.FILL_HORIZONTAL);
			inputsLabelData.horizontalSpan = 6;
			inputsLabel.setText("Required Inputs:");
			inputsLabel.setLayoutData(inputsLabelData);
			
			inputsList = new ListViewer(topPanel, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL| SWT.H_SCROLL);
			GridData inputsListData = new GridData(GridData.FILL_BOTH);
			inputsListData.horizontalSpan = 6;
			inputsList.getList().setLayoutData(inputsListData);
			inputsList.setLabelProvider(inputListLabelProvider);
			if (inputsMap != null && inputsMap.size() > 0) {
				for (Entry<String, EClass> c : inputsMap.entrySet()) {
					inputsList.add(c.getKey());
				}
			}
			
			Label emptyLabel3 = new Label(topPanel, SWT.NONE);
			GridData emptyLabelData3 = new GridData();
			emptyLabelData3.horizontalSpan = 4;
			emptyLabel3.setText("");
			emptyLabel3.setLayoutData(emptyLabelData3);
			
			Button addInputButton = new Button(topPanel, SWT.PUSH | SWT.CENTER);
			GridData addButData = new GridData(GridData.FILL_HORIZONTAL);
			addButData.horizontalSpan = 1;
			addInputButton.setLayoutData(addButData);
			addInputButton.setText("Add Input");
			
			removeInputButton = new Button(topPanel, SWT.PUSH | SWT.CENTER);
			GridData removeButData = new GridData(GridData.FILL_HORIZONTAL);
			removeButData.horizontalSpan = 1;
			removeInputButton.setLayoutData(removeButData);
			removeInputButton.setText("Remove Input");
			removeInputButton.setEnabled(false);
			
			Label outputsLabel = new Label(topPanel, SWT.NONE);
			GridData outputsLabelData = new GridData(GridData.FILL_HORIZONTAL);
			outputsLabelData.horizontalSpan = 6;
			outputsLabel.setText("Required Outputs:");
			outputsLabel.setLayoutData(outputsLabelData);
			
			outputsList = new ListViewer(topPanel, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL| SWT.H_SCROLL);
			GridData outputsListData = new GridData(GridData.FILL_BOTH);
			outputsListData.horizontalSpan = 6;
			outputsList.getList().setLayoutData(outputsListData);
			outputsList.setLabelProvider(outputListLabelProvider);
			if (outputsMap != null && outputsMap.size() > 0) {
				for (Entry<String, EClass> c : outputsMap.entrySet()) {
					outputsList.add(c.getKey());
				}
			}
			
			Label emptyLabel4 = new Label(topPanel, SWT.NONE);
			GridData emptyLabelData4 = new GridData();
			emptyLabelData4.horizontalSpan = 4;
			emptyLabel4.setText("");
			emptyLabel4.setLayoutData(emptyLabelData4);
			
			Button addOutputButton = new Button(topPanel, SWT.PUSH | SWT.CENTER);
			GridData addOutButData = new GridData(GridData.FILL_HORIZONTAL);
			addOutButData.horizontalSpan = 1;
			addOutputButton.setLayoutData(addOutButData);
			addOutputButton.setText("Add Output");
			
			removeOutputButton = new Button(topPanel, SWT.PUSH | SWT.CENTER);
			GridData removeOutButData = new GridData(GridData.FILL_HORIZONTAL);
			removeOutButData.horizontalSpan = 1;
			removeOutputButton.setLayoutData(removeOutButData);
			removeOutputButton.setText("Remove Output");
			removeOutputButton.setEnabled(false);
			
			helpLabel = new Label(topPanel, SWT.NONE);
			GridData helpLabelData = new GridData(GridData.FILL_HORIZONTAL);
			helpLabelData.horizontalSpan = 6;
			helpLabel.setText("");
			helpLabel.setLayoutData(helpLabelData);
			
			extraOptionsGroup = new Group(topPanel, SWT.NONE);
			GridData extraOptionsGroupData = new GridData(GridData.FILL_BOTH);
			extraOptionsGroupData.horizontalSpan = 6;
			extraOptionsGroup.setLayoutData(extraOptionsGroupData);
			extraOptionsGroup.setText("Additional Options");
			extraOptionsGroup.setLayout(new FillLayout());
			extraOptionsGroup.setVisible(false);
			
			if (engine != null) {
//				toc = ModelTransformatorPlugin.getService().getEngineAdditionalOptionsComposite(engine);
//				
//				if (toc != null) {
//					toc.setParent(extraOptionsGroup);
//					toc.createControls();
//					extraOptionsGroup.setVisible(true);
//					extraOptionsGroup.pack();
//				}
				
				// TODO Try to build the options composite
			}
			
			
			
			// Add Listeners
			nameText.addModifyListener(textListener);
			descriptionText.addModifyListener(textListener);
			addFileButton.addSelectionListener(addFileButtonListener);
			removeFileButton.addSelectionListener(removeFileButtonListener);
			inputsList.addSelectionChangedListener(inputListListener);
			outputsList.addSelectionChangedListener(outputListListener);
			fileList.addSelectionChangedListener(fileListListener);
			addInputButton.addSelectionListener(addInputButtonListener);
			addOutputButton.addSelectionListener(addOutputButtonListener);
			removeInputButton.addSelectionListener(removeInputButtonListener);
			removeOutputButton.addSelectionListener(removeOutputButtonListener);
			engineCombo.addSelectionListener(comboListener);
			
			
			// Add explanation text
			if (name == null || name.equals("")) {
				setTitle("Create a new Transformation");
			}
			else {
				setTitle("Edit a Transformation");
			}
			setDescription("Register a transformation in the GEMDE transformation service.");
			
			// Set the control!!
			setControl(topPanel);
		}
		
		private ModifyListener textListener = new ModifyListener () {

			@Override
			public void modifyText(ModifyEvent e) {
				name = nameText.getText();
				description = descriptionText.getText();
				updateFinish();
			}
			
		};
		
		private ISelectionChangedListener inputListListener = new ISelectionChangedListener() {
			
			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				if (inputsList.getSelection() != null && !inputsList.getSelection().isEmpty()) {
					removeInputButton.setEnabled(true);
				}
				else {
					removeInputButton.setEnabled(false);
				}
			}
		};
		
		private ISelectionChangedListener outputListListener = new ISelectionChangedListener() {
			
			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				if (outputsList.getSelection() != null && !outputsList.getSelection().isEmpty()) {
					removeOutputButton.setEnabled(true);
				}
				else {
					removeOutputButton.setEnabled(false);
				}
			}
		};
		
		private ISelectionChangedListener fileListListener = new ISelectionChangedListener() {
			
			@Override
			public void selectionChanged(SelectionChangedEvent event) {
				if (fileList.getSelection() != null && !fileList.getSelection().isEmpty()) {
					removeFileButton.setEnabled(true);
				}
				else {
					removeFileButton.setEnabled(false);
				}
			}
		};
		
		private SelectionListener addFileButtonListener = new SelectionListener() {

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// Not needed
				
			}

			@Override
			public void widgetSelected(SelectionEvent e) {
				FileDialog dialog = new FileDialog(getShell());
				dialog.setText("Select a transformation file to be registered");
				String path = dialog.open();
				
				if (path != null) {
					URI uri = new File(path).toURI();
					transformationFiles.add(uri);
					fileList.add(uri);
//					checkNeeded = true;
					transformationFilesCorrect = false;
				}
				updateFinish();
			}
			
		};
		
		private SelectionListener addInputButtonListener = new SelectionListener() {

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}

			@Override
			public void widgetSelected(SelectionEvent e) {
				AddTransformationInputDialog dialog = new AddTransformationInputDialog(new Shell());
				if (dialog.open() == Dialog.OK) {
					EClass c = dialog.getEClassResult();
					String name = dialog.getInputName();
					if (name != null && c != null) {
						inputsMap.put(name, c);
						inputsList.add(name);
					}
				}
				updateFinish();
			}
			
		};
		
		private SelectionListener removeInputButtonListener = new SelectionListener() {

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}

			@Override
			public void widgetSelected(SelectionEvent e) {
				if (removeInputButton.isEnabled()) {
					Object input = ((StructuredSelection)inputsList.getSelection()).getFirstElement();
					if (input instanceof String) {
						inputsMap.remove(input);
						inputsList.remove(input);
					}
				}
				updateFinish();
			}
			
		};
		
		private SelectionListener addOutputButtonListener = new SelectionListener() {

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}

			@Override
			public void widgetSelected(SelectionEvent e) {
				AddTransformationInputDialog dialog = new AddTransformationInputDialog(new Shell());
				if (dialog.open() == Dialog.OK) {
					EClass c = dialog.getEClassResult();
					String name = dialog.getInputName();
					if (name != null && c != null) {
						outputsMap.put(name, c);
						outputsList.add(name);
					}
				}
				updateFinish();
			}
			
		};
		
		private SelectionListener removeOutputButtonListener = new SelectionListener() {

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}

			@Override
			public void widgetSelected(SelectionEvent e) {
				if (removeOutputButton.isEnabled()) {
					Object output = ((StructuredSelection)outputsList.getSelection()).getFirstElement();
					if (output instanceof String) {
						outputsMap.remove(output);
						outputsList.remove(output);
					}
				}
				updateFinish();
			}
			
		};
		
		private SelectionListener removeFileButtonListener = new SelectionListener() {

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// Not needed
				
			}

			@Override
			public void widgetSelected(SelectionEvent e) {
				if (removeFileButton.isEnabled()) {
					URI uri = (URI)((StructuredSelection)fileList.getSelection()).getFirstElement();
					fileList.remove(uri);
					transformationFiles.remove(uri);
//					checkNeeded = true;
					transformationFilesCorrect = false;
				}
				updateFinish();
			}
			
		};
		
		private SelectionListener comboListener = new SelectionListener() {

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// Not needed
			}

			@Override
			public void widgetSelected(SelectionEvent e) {
				for (Control c : extraOptionsGroup.getChildren()) {
					c.dispose();
				}
				
				
				engine = engineCombo.getItem(engineCombo.getSelectionIndex());
				
//				extraOptionsGroup.setVisible(toc != null);
//				if (toc != null) {
//					toc.addOptionsListener(optListener);
//					toc.setParent(extraOptionsGroup);
//					toc.setExtraOptions(options != null ? options : new HashMap<String, String>());
//					toc.createControls();
//					extraOptionsGroup.pack();
//					getShell().pack();
//				}
				
				
				
				// Update the page status is the combo selection changes.
				updateFinish();
			}
			
		};
		
		private LabelProvider inputListLabelProvider = new LabelProvider(){
			public Image getImage(Object element) {
				return null;
			}
			public String getText(Object element) {
				if (element != null && element instanceof String) {
					String name = (String)element;
					EClass eclass = inputsMap.get(name);
					if (eclass != null) {
						return name + ": " + eclass.getInstanceTypeName();
					}
					else {
						return "Null";
					}
				}
				else {
					return "Null";
				}
			}
		};
		
		private LabelProvider outputListLabelProvider = new LabelProvider(){
			public Image getImage(Object element) {
				return null;
			}
			public String getText(Object element) {
				if (element != null && element instanceof String) {
					String name = (String)element;
					EClass eclass = outputsMap.get(name);
					if (eclass != null) {
						return name + ": " + eclass.getInstanceTypeName();
					}
					else {
						return "Null";
					}
				}
				else {
					return "Null";
				}
			}
		};
		
		private LabelProvider fileLabelProvider = new LabelProvider(){
			public Image getImage(Object element) {
				return null;
			}
			public String getText(Object element) {
				if (element != null && element instanceof URI) {
					return ((URI)element).toString();
				}
				else {
					return "Null";
				}
			}
		};
		
		
		/**
		 * Internal method called after a listener has been triggered
		 */
		private void updateFinish () {
			// If all the required fields have been completely filled in we can finish the wizard
			helpLabel.setText("");
			engine = engineCombo.getItem(engineCombo.getSelectionIndex());
			transformationFilesCorrect = ModelTransformatorPlugin.getService().checkTransformationValidity(engine, transformationFiles);
			ioParamsCorrect = ModelTransformatorPlugin.getService().checkTransformationParamsValidity(engine, inputsMap, outputsMap, options);
			
			if (name == null || name.equals("")) {
				canFinish = false;
				helpLabel.setText("The transformation must have a valid name");
			}
			else if (transformationFiles == null || transformationFiles.size() == 0) {
				canFinish = false;
				helpLabel.setText("The transformation requires at least a source file.");
			}
			else if (!transformationFilesCorrect) {
				canFinish = false;
				helpLabel.setText("The selected files are not a valid transformation file for the selected engine.");
			}
			else if (!ioParamsCorrect) {
				canFinish = false;
				helpLabel.setText("The provided input/output parameters are not valid for the current engine.");
			}
			else {
				canFinish = true;
			}
			
			setPageComplete(canFinish);
			
		}
	}
	

}
